﻿using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Assertions;

namespace Go
{
	public class Board
	{
		public int num_rows;
		public int num_cols;
		Dictionary<Point, GoString> _grid;
		public Board(int num_rows, int num_cols)
		{
			this.num_rows = num_rows;
			this.num_cols = num_cols;
			this._grid = new Dictionary<Point, GoString>();
		}

		HashSet<GoString> adjacent_same_color = new HashSet<GoString>();
		HashSet<GoString> adjacent_opposite_color = new HashSet<GoString>();
		HashSet<Point> liberties = new HashSet<Point>();
		public void place_stone(Player player, Point point)
		{
			Assert.IsTrue(is_on_grid(point));
			Assert.IsTrue(_grid.ContainsKey(point) == false);

			adjacent_same_color.Clear();
			adjacent_opposite_color.Clear();
			liberties.Clear();

			foreach (Point neighbor in point.neighbors())
			{
				if (!is_on_grid(neighbor))
					continue;
			
				bool has_stone = _grid.TryGetValue(neighbor, out GoString neighbor_string);

				if (!has_stone)
				{
					liberties.Add(neighbor);
				}
				else if (neighbor_string.color == player)
				{
					adjacent_same_color.Add(neighbor_string);
				}
				else
				{
					adjacent_opposite_color.Add(neighbor_string);
				}
			}
			var new_string = new GoString(player, new HashSet<Point>() { point }, liberties);

			foreach (var same_color_string in adjacent_same_color)
			{
				new_string = new_string.merge_with(same_color_string);
			}
			foreach (var new_string_point in new_string.stones)
			{
				_grid[new_string_point] = new_string;
			}
			foreach (var other_color_string in adjacent_opposite_color)
			{
				other_color_string.remove_liberty(point);
			}
			foreach (var other_color_string in adjacent_opposite_color)
			{
				if (other_color_string.num_liberties == 0)
				{
					_remove_string(other_color_string);
				}
			}
		}

		public bool is_on_grid(Point point)
		{
			return 1 <= point.row && point.row <= num_rows && 1 <= point.col && point.col <= num_cols;
		}

		public Player get(Point point)
		{
			bool has_stone = _grid.TryGetValue(point, out GoString _string);
			if (!has_stone)
				return Player.none;
			return _string.color;
		}

		public GoString get_go_string(Point point)
		{
			bool has_stone = _grid.TryGetValue(point, out GoString _string);
			if (!has_stone)
				return null;
			return _string;
		}

		void _remove_string(GoString _string)
		{
			foreach (Point point in _string.stones)
			{
				foreach (var neighbor in point.neighbors())
				{
					bool has_stone = _grid.TryGetValue(neighbor, out GoString neighbor_string);
					if (!has_stone)
						continue;
					
					if (neighbor_string != _string)
						neighbor_string.add_liberty(point);
				}
				_grid.Remove(point);
			}
		}
		public Board DeepCopy()
		{
			Board board = new Board(num_rows, num_cols);
			Dictionary<GoString, Point> strings = new Dictionary<GoString, Point>();
			foreach (Point point in _grid.Keys)
			{
				var _string = _grid[point];
				if (strings.ContainsKey(_string))
				{
					board._grid.Add(point, board._grid[strings[_string]]);
				}
				else
				{
					strings.Add(_string, point);
					board._grid.Add(point, new GoString(_string.color, new HashSet<Point>(_string.stones), new HashSet<Point>(_string.liberties)));
				}
			}
			return board;
		}
		public static bool operator==(Board lhs, Board rhs)
		{
			if (lhs.num_rows == rhs.num_rows && lhs.num_cols == rhs.num_cols)
			{
				for (int i = 1; i <= lhs.num_rows; i++)
				{
					for (int j = 1; j <= rhs.num_cols; j++)
					{
						Point p = new Point(i, j);
						bool lc = lhs._grid.ContainsKey(p);
						bool rc = rhs._grid.ContainsKey(p);
						if (lc && rc)
						{
							if (lhs._grid[p].color != rhs._grid[p].color) return false;
						}
						else if (lc != rc) return false;
					}
				}
				return true;
			}
			return false;
		}
		public static bool operator !=(Board lhs, Board rhs)
		{
			if (lhs.num_rows == rhs.num_rows && lhs.num_cols == rhs.num_cols)
			{
				for (int i = 1; i <= lhs.num_rows; i++)
				{
					for (int j = 1; j <= rhs.num_cols; j++)
					{
						Point p = new Point(i, j);
						bool lc = lhs._grid.ContainsKey(p);
						bool rc = rhs._grid.ContainsKey(p);
						if (lc && rc)
						{
							if (lhs._grid[p].color != rhs._grid[p].color) return true;
						}
						else if (lc != rc) return true;
					}
				}
				return false;
			}
			return true;
		}
		public override bool Equals(object obj)
		{
			return this == (Board)obj;
		}
		public override int GetHashCode()
		{
			return base.GetHashCode();
		}
	}
}